package baseFx.database.execute;

import baseFx.common.Assert;
import baseFx.common.EventDispatcher;
import baseFx.common.utils.ConvertUtils;
import baseFx.database.*;
import baseFx.database.query.DbEntityQuery;
import baseFx.database.where.ExecuteWhere;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public abstract class AbstractDbExecute extends AbstractDbEntityCore implements DbExecute {
    private static final String beforeDispatcherKey = UUID.randomUUID().toString();
    private static final String afterDispatcherKey = UUID.randomUUID().toString();
    private static final String errorDispatcherKey = UUID.randomUUID().toString();
    private static final String tranBeforeDispatcherKey = UUID.randomUUID().toString();
    private static final String tranAfterDispatcherKey = UUID.randomUUID().toString();
    private Map<String, Object> data = new HashMap<>();
    private EventDispatcher<DbExecuteBeforeArgs> beforeDispatcher = new EventDispatcher<>(beforeDispatcherKey);
    private EventDispatcher<DbExecuteAfterArgs> afterDispatcher = new EventDispatcher<>(afterDispatcherKey);
    private EventDispatcher<DbExecuteErrorArgs> errorDispatcher = new EventDispatcher<>(errorDispatcherKey);
    private EventDispatcher<TransFinishArgs> tranBeforeDispatcher = new EventDispatcher<>(tranBeforeDispatcherKey);
    private EventDispatcher<TransFinishArgs> tranAfterDispatcher = new EventDispatcher<>(tranAfterDispatcherKey);
    private Sql sql;
    private DbEntityQuery query;
    private String eidTranBefore;
    private String eidTranAfter;

    protected abstract SqlParam createInsert(SqlInsertContext context);

    protected abstract String getIncSql(String column);

    protected abstract SqlParam createUpdate(SqlUpdateContext context);

    protected abstract SqlParam createDelete(ExecuteWhere<?> where, Integer timeout);

    protected abstract SqlParam createProc(Object model, Integer timeout);

    public AbstractDbExecute(Sql sql, DbEntityQuery query) {
        super(sql);
        this.sql = sql;
        this.query = query;
        eidTranBefore = this.sql.eventTransactionBefore().add((s, e) -> onTransactionBefore(e));
        eidTranAfter = this.sql.eventTransactionAfter().add((s, e) -> onTransactionAfter(e));
    }

    protected void onError(DbExecuteErrorArgs args) {
        errorDispatcher.fire(errorDispatcherKey, this, args);
    }

    protected void onAfter(DbExecuteAfterArgs args) {
        afterDispatcher.fire(afterDispatcherKey, this, args);
    }

    protected void onBefore(DbExecuteBeforeArgs args) {
        beforeDispatcher.fire(beforeDispatcherKey, this, args);
    }

    protected void onTransactionBefore(TransFinishArgs args) {
        tranBeforeDispatcher.fire(tranBeforeDispatcherKey, this, args);
    }

    protected void onTransactionAfter(TransFinishArgs args) {
        tranAfterDispatcher.fire(tranAfterDispatcherKey, this, args);
    }

    @Override
    public Map<String, Object> userdata() {
        return data;
    }

    @Override
    public String paramName(String name) {
        return sql.paramName(name);
    }

    @Override
    public String escapeField(String field) {
        return sql.escapeField(field);
    }

    @Override
    public void changeDataSource(String key) {
        sql.changeDataSource(key);
    }

    @Override
    public DbEntityQuery query() {
        return query;
    }

    @Override
    public EventDispatcher<DbExecuteErrorArgs> eventError() {
        return errorDispatcher;
    }

    @Override
    public EventDispatcher<DbExecuteBeforeArgs> eventBefore() {
        return beforeDispatcher;
    }

    @Override
    public EventDispatcher<DbExecuteAfterArgs> eventAfter() {
        return afterDispatcher;
    }

    @Override
    public <T> int insert(T model, Integer timeout) {
        Class<?> table = model.getClass();
        DbExecuteBeforeArgs before = new DbExecuteBeforeArgs(DbExecuteOperation.insert, table, model);
        try {
            onBefore(before);
            if (before.result == null) {
                DbEntity entity = DbEntity.get(table);
                SqlInsertContext ctx = new SqlInsertContext();
                ctx.model = model;
                ctx.table = table;
                ctx.timeout = timeout;
                SqlParam param = createInsert(ctx);
                before.result = sql.execute(param);
                for (DbEntity.DbField field : entity.getIncFields()) {
                    Object val = field.get(model);
                    if (val == null) {
                        Long id = sql.queryOne(Long.class, getIncSql(field.dbName));
                        field.set(model, ConvertUtils.change(id.toString(), field.type));
                    }
                }
            }
            onAfter(before.toAfter());
            return (int) before.result;
        } catch (Exception ex) {
            onError(before.toError(ex));
            throw ex;
        }
    }

    private int executeUpdate(DbExecuteUpdateArgs args, Integer timeout) {
        SqlUpdateContext ctx = new SqlUpdateContext();
        ctx.isSetNull = args.getIsSetNull();
        ctx.timeout = timeout;
        ctx.setTable = args.getSetTable();
        ctx.set = args.getSet();
        ctx.where = args.getWhere();
        SqlParam param = createUpdate(ctx);
        return sql.execute(param);
    }

    @Override
    public <T> int update(T set, ExecuteWhere<T> where, Integer timeout) {
        DbExecuteUpdateArgs args = new DbExecuteUpdateArgs(where.getTable(), set, where, false);
        DbExecuteBeforeArgs before = new DbExecuteBeforeArgs(DbExecuteOperation.update, where.getTable(), args);
        try {
            onBefore(before);
            if (before.result == null) {
                before.result = executeUpdate(args, timeout);
            }
            onAfter(before.toAfter());
            return (int) before.result;
        } catch (Exception ex) {
            onError(before.toError(ex));
            throw ex;
        }
    }

    @Override
    public <T> int setNull(T set, ExecuteWhere<T> where, Integer timeout) {
        DbExecuteUpdateArgs args = new DbExecuteUpdateArgs(where.getTable(), set, where, true);
        DbExecuteBeforeArgs before = new DbExecuteBeforeArgs(DbExecuteOperation.update, where.getTable(), args);
        try {
            onBefore(before);
            if (before.result == null) {
                before.result = executeUpdate(args, timeout);
            }
            onAfter(before.toAfter());
            return (int) before.result;
        } catch (Exception ex) {
            onError(before.toError(ex));
            throw ex;
        }
    }

    @Override
    public <T> int delete(ExecuteWhere<T> where, boolean force, Integer timeout) {
        DbEntity entity = DbEntity.get(where.getTable());
        Assert.isTrue(entity.isAllowDelete() || force, entity.getTableName() + " 不允许删除数据");
        DbExecuteBeforeArgs before = new DbExecuteBeforeArgs(DbExecuteOperation.delete, where.getTable(), where);
        try {
            onBefore(before);
            if (before.result == null) {
                SqlParam param = createDelete(where, timeout);
                before.result = sql.execute(param);
            }
            onAfter(before.toAfter());
            return (int) before.result;
        } catch (Exception ex) {
            onError(before.toError(ex));
            throw ex;
        }
    }

    @Override
    public int proc(Object model, Integer timeout) {
        DbExecuteBeforeArgs before = new DbExecuteBeforeArgs(DbExecuteOperation.proc, model.getClass(), model);
        try {
            onBefore(before);
            if (before.result == null) {
                SqlParam param = createProc(model, timeout);
                before.result = sql.execute(param);
            }
            onAfter(before.toAfter());
            return (int) before.result;
        } catch (Exception ex) {
            onError(before.toError(ex));
            throw ex;
        }
    }

    @Override
    public boolean isClosed() {
        return sql == null || sql.isClosed();
    }

    @Override
    public void close() {
        query = null;
        if (beforeDispatcher != null) {
            beforeDispatcher.clear();
            beforeDispatcher = null;
        }
        if (afterDispatcher != null) {
            afterDispatcher.clear();
            afterDispatcher = null;
        }
        if (errorDispatcher != null) {
            errorDispatcher.clear();
            errorDispatcher = null;
        }
        if (tranBeforeDispatcher != null) {
            tranBeforeDispatcher.clear();
            tranBeforeDispatcher = null;
        }
        if (tranAfterDispatcher != null) {
            tranAfterDispatcher.clear();
            tranAfterDispatcher = null;
        }
        if (data != null) {
            data.clear();
            data = null;
        }
        if (sql != null) {
            if (sql.isClosed() == false) {
                sql.eventTransactionBefore().remove(eidTranBefore);
                sql.eventTransactionAfter().remove(eidTranAfter);
            }
            sql = null;
        }
    }

    @Override
    public EventDispatcher<TransFinishArgs> eventTransactionBefore() {
        return tranBeforeDispatcher;
    }

    @Override
    public EventDispatcher<TransFinishArgs> eventTransactionAfter() {
        return tranAfterDispatcher;
    }

    @Override
    public Transaction transaction(TransactionLevel level) {
        return sql.transaction(level);
    }

    @Override
    public boolean inTransaction() {
        return sql.inTransaction();
    }
}
